#include "Heap.h"

namespace Hush
{
    namespace Database
    {
        RowId SimpleHeap::InsertRow(const RowPtr& rowPtr)
        {
            DataPage* currentPage = this->GetLastPage();
            if (currentPage->GetAvailableSpace() < rowPtr.length)
            {
                currentPage = this->bufferManager->AllocatePage<DataPage>();
                this->GetLastPage()->SetNextPageId(currentPage->GetPageId());
                this->SetLastPage(currentPage->GetPageId());
            }

            return currentPage->InsertRow(rowPtr);
        }

        RowId Heap::InsertRow(const RowPtr& rowPtr)
        {            
            RowId resultRowId;

            DataPage* currentPage = this->bufferManager->GetPageAs<DataPage>(currentDataPageId);
            PageId directoryPageId = this->currentDirectoryRowId.pageId;
            DirectoryPage::Entry* dirEntry = Null;
            
            if (currentPage->GetAvailableSpace() >= rowPtr.length)
            {
                DirectoryPage* dirPage = this->bufferManager->GetPageAs<DirectoryPage>(directoryPageId);
                dirEntry = (DirectoryPage::Entry*)(dirPage->GetRowPtr(currentDirectoryRowId).data);
            }
            else
            {
                // Need to find a new page with sufficent space
                directoryPageId = this->headerPageId;
                while (directoryPageId != EndOfPage)
                { 
                    DirectoryPage* dirPage = this->bufferManager->GetPageAs<DirectoryPage>(directoryPageId);
                    this->currentDirectoryRowId = dirPage->GetFirstRow();
                    while (currentDirectoryRowId.slotId != EndOfSlot)
                    {
                        dirEntry = (DirectoryPage::Entry*)(dirPage->GetRowPtr(currentDirectoryRowId).data);
                        if (dirEntry->freeSpace >= rowPtr.length)
                        {                            
                            break;
                        }
                        currentDirectoryRowId = dirPage->GetNextRow(currentDirectoryRowId);
                    }

                    if (currentDirectoryRowId.slotId != EndOfSlot)
                    {
                        currentDataPageId = dirEntry->pageId;
                        break;
                    }
                    else
                    {
                        directoryPageId = dirPage->GetNextPageId();
                    }
                }

                if (directoryPageId == EndOfPage)
                { 
                    // No available page. Need to allocate a new one
                    DataPage* newPage = bufferManager->AllocatePage<DataPage>();

                    // Find a dir page to insert the new page info
                    PageId dirPageId = this->headerPageId;  
                    DirectoryPage* dirPage = Null;

                    while(dirPageId != EndOfPage)
                    {
                        dirPage = this->bufferManager->GetPageAs<DirectoryPage>(dirPageId);
                        if (dirPage->GetAvailableSpace() >= sizeof(DirectoryPage::Entry))
                        {
                            break;
                        }
                        dirPageId = dirPage->GetNextPageId();
                    }

                    if (dirPageId == EndOfPage)
                    {
                        // All dir pages are full. Need to create a new one
                        dirPage = this->bufferManager->AllocatePage<DirectoryPage>();
                        dirPage->SetNextPageId(this->headerPageId);
                        this->headerPageId = dirPage->GetPageId();
                    }

                    this->currentDirectoryRowId = dirPage->AddEntry(newPage->GetPageId(), newPage->GetAvailableSpace());
                    dirEntry = (DirectoryPage::Entry*)(dirPage->GetRowPtr(currentDirectoryRowId).data);
                    currentPage = newPage;
                    
                }
                else
                {
                    currentPage = this->bufferManager->GetPageAs<DataPage>(currentDataPageId);
                }               
            }

            resultRowId = currentPage->InsertRow(rowPtr);
            dirEntry->freeSpace = currentPage->GetAvailableSpace(); 

            return resultRowId;
        }
    }
}